/*
 * 描          述:  <描述>
 * 修  改   人:  Administrator
 * 修改时间:  2020年3月2日
 * <修改描述:>
 */
package com.tx.component.file.resource.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.StringUtils;

import com.tx.component.file.exception.FileContextResourceException;
import com.tx.component.file.resource.FCResource;
import com.tx.component.file.resource.FCResourceFilter;
import com.tx.component.file.resource.FCResourceLoader;
import com.tx.component.file.resource.FCResourceTypeEnum;
import com.tx.component.file.util.FileContextUtils;
import com.tx.core.exceptions.resource.ResourceAccessException;
import com.tx.core.exceptions.util.AssertUtils;
import com.tx.core.util.MessageUtils;

/**
 * 基于spring的Resource实现的资源加载器<br/>
 * <功能详细描述>
 * 
 * @author  Administrator
 * @version  [版本号, 2020年3月2日]
 * @see  [相关类/方法]
 * @since  [产品/模块版本]
 */
public class LocalAwareFCResourceLoader
        implements FCResourceLoader, InitializingBean {
    
    /** 日志记录句柄 */
    protected static Logger logger = LoggerFactory
            .getLogger(FCResourceLoader.class);
    
    /** 文件夹目录 */
    private String catalog;
    
    /** 目录文件资源 */
    private FileSystemResource localCatalogResource;
    
    /** 真实的资源加载器 */
    private FCResourceLoader resourceLoader;
    
    /** <默认构造函数> */
    public LocalAwareFCResourceLoader() {
        super();
    }
    
    /** <默认构造函数> */
    public LocalAwareFCResourceLoader(String catalog, String path,
            FCResourceLoader resourceLoader) {
        super();
        this.catalog = catalog;
        this.localCatalogResource = new FileSystemResource(path);
        this.resourceLoader = resourceLoader;
    }
    
    /**
     * @throws IOException 
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() {
        AssertUtils.notEmpty(this.catalog, "catalog is empty.");
        
        //初始化资源目录
        if (!this.localCatalogResource.exists()) {
            try {
                FileUtils.forceMkdir(this.localCatalogResource.getFile());
            } catch (IOException e) {
                throw new ResourceAccessException("初始化文件资源目录异常，文件夹创建失败.");
            }
        }
        if (!this.localCatalogResource.getFile().isDirectory()) {
            throw new ResourceAccessException("初始化文件资源目录异常，文件或已存在并且不为目录.");
        }
    }
    
    /**
     * @param relativePath
     * @return
     */
    @Override
    public FCResource getFCResource(String relativePath) {
        relativePath = relativePath == null ? "" : relativePath;
        //预处理关联路径
        relativePath = FileContextUtils.handleRelativePath(relativePath);
        
        //创建关联资源
        FCResource fileResource = new LocalAwareFCResource(this.catalog,
                this.localCatalogResource, this.resourceLoader, relativePath);
        return fileResource;
    }
    
    /**
     * @param relativePath
     * @param type
     * @return
     */
    @Override
    public FCResource getFCResource(String relativePath,
            FCResourceTypeEnum type) {
        AssertUtils.notNull(type, "type is null.");
        
        relativePath = relativePath == null ? "" : relativePath;
        //预处理关联路径
        relativePath = FileContextUtils.handleRelativePath(relativePath);
        
        //创建关联资源
        FCResource fileResource = new LocalAwareFCResource(this.catalog,
                this.localCatalogResource, this.resourceLoader, relativePath,
                type);
        return fileResource;
    }
    
    /**
     * @return 返回 catalog
     */
    public String getCatalog() {
        return catalog;
    }
    
    /**
     * @param 对catalog进行赋值
     */
    public void setCatalog(String catalog) {
        this.catalog = catalog;
    }
    
    /**
     * @return 返回 resourceLoader
     */
    public FCResourceLoader getResourceLoader() {
        return resourceLoader;
    }
    
    /**
     * @param 对resourceLoader进行赋值
     */
    public void setResourceLoader(FCResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
    
    /**
     * @return 返回 localCatalogResource
     */
    public FileSystemResource getLocalCatalogResource() {
        return localCatalogResource;
    }
    
    /**
     * @param 对localCatalogResource进行赋值
     */
    public void setLocalCatalogResource(
            FileSystemResource localCatalogResource) {
        this.localCatalogResource = localCatalogResource;
    }
    
    /**
     * SFSRFileResource实现
     * Spring_FileSystemResource_FileResource
     * <功能详细描述>
     * 
     * @author  Administrator
     * @version  [版本号, 2020年3月2日]
     * @see  [相关类/方法]
     * @since  [产品/模块版本]
     */
    public static class LocalAwareFCResource implements FCResource {
        
        /** 并发锁 */
        private final static int LOCK_COUNT = 32;
        
        /** 锁 ,最多只能同时进行32张图片的压缩 */
        private final static Object[] LOCKS = new Object[LOCK_COUNT];
        
        //初始化锁
        static {
            for (int i = 0; i < LOCK_COUNT; i++) {
                LOCKS[i] = new Object();
            }
        }
        
        /**
         * 根据路径获取锁<br/>
         * @param relativePath 相对路径
         *            
         * @return Object 锁
         * @exception [异常类型] [异常说明]
         * @see [类、类#方法、类#成员]
         */
        private static Object getLock(String relativePath) {
            int hashCode = relativePath.hashCode();
            return LOCKS[Math.abs(hashCode) % LOCKS.length];
        }
        
        /** 文件定义 */
        private String catalog;
        
        /** 文件资源 */
        private FileSystemResource localCatalogResource;
        
        /** resourceLoader */
        private FCResourceLoader resourceLoader;
        
        /** 文件资源 */
        private FileSystemResource localResource;
        
        /** 本地的资源 */
        private FCResource resource;
        
        /** <默认构造函数> */
        public LocalAwareFCResource(String catalog,
                FileSystemResource localCatalogResource,
                FCResourceLoader resourceLoader, String relativePath,
                FCResourceTypeEnum type) {
            super();
            AssertUtils.notEmpty(catalog, "catalog is empty.");
            AssertUtils.notNull(localCatalogResource,
                    "localCatalogResource is null.");
            AssertUtils.notNull(resourceLoader, "resourceLoader is null.");
            AssertUtils.notEmpty(relativePath, "relativePath is empty.");
            
            this.catalog = catalog;
            this.localCatalogResource = localCatalogResource;
            this.resourceLoader = resourceLoader;
            this.resource = resourceLoader.getFCResource(relativePath);
            
            //预处理相对路径
            relativePath = FileContextUtils.handleRelativePath(relativePath);
            
            //获取对应的文件
            try {
                this.resource = this.resourceLoader.getFCResource(relativePath,
                        type);
                this.localResource = new FileSystemResource(
                        StringUtils.applyRelativePath(
                                this.localCatalogResource.getFile()
                                        .getCanonicalPath() + "/",
                                relativePath));
            } catch (Exception e) {
                throw new ResourceAccessException("创建LocalFileFCResource异常.",
                        e);
            }
        }
        
        /** <默认构造函数> */
        public LocalAwareFCResource(String catalog,
                FileSystemResource localCatalogResource,
                FCResourceLoader resourceLoader, String relativePath) {
            super();
            AssertUtils.notEmpty(catalog, "catalog is empty.");
            AssertUtils.notNull(localCatalogResource,
                    "localCatalogResource is null.");
            AssertUtils.notNull(resourceLoader, "resourceLoader is null.");
            AssertUtils.notEmpty(relativePath, "relativePath is empty.");
            
            this.catalog = catalog;
            this.localCatalogResource = localCatalogResource;
            this.resourceLoader = resourceLoader;
            this.resource = resourceLoader.getFCResource(relativePath);
            
            //预处理相对路径
            relativePath = FileContextUtils.handleRelativePath(relativePath);
            
            //获取对应的文件
            try {
                this.resource = this.resourceLoader.getFCResource(relativePath);
                this.localResource = new FileSystemResource(
                        StringUtils.applyRelativePath(
                                this.localCatalogResource.getFile()
                                        .getCanonicalPath() + "/",
                                relativePath));
            } catch (Exception e) {
                throw new ResourceAccessException("创建LocalFileFCResource异常.",
                        e);
            }
        }
        
        /** <默认构造函数> */
        public LocalAwareFCResource(String catalog,
                FileSystemResource localCatalogResource,
                FCResourceLoader resourceLoader, FCResource resource) {
            super();
            AssertUtils.notEmpty(catalog, "catalog is empty.");
            AssertUtils.notNull(localCatalogResource,
                    "localCatalogResource is null.");
            AssertUtils.notNull(resourceLoader, "resourceLoader is null.");
            AssertUtils.notNull(resource, "resource is null.");
            
            this.catalog = catalog;
            this.localCatalogResource = localCatalogResource;
            this.resourceLoader = resourceLoader;
            this.resource = resource;
            
            //获取对应的文件
            try {
                this.localResource = new FileSystemResource(
                        StringUtils.applyRelativePath(
                                this.localCatalogResource.getFile()
                                        .getCanonicalPath() + "/",
                                resource.getRelativePath()));
            } catch (Exception e) {
                throw new ResourceAccessException("创建LocalFileFCResource异常.",
                        e);
            }
        }
        
        /**
         * @return 返回 relativePath
         */
        @Override
        public String getRelativePath() {
            return this.resource.getRelativePath();
        }
        
        /**
         * @return
         */
        @Override
        public String getCatalog() {
            return this.catalog;
        }
        
        /**
         * @return
         * @throws FileContextResourceException
         */
        @Override
        public FCResourceTypeEnum getType()
                throws FileContextResourceException {
            return this.resource.getType();
        }
        
        /**
         * @param relativePath
         * @return
         */
        @Override
        public FCResource createRelative(String relativePath) {
            //拼接相对路径
            String pathToUse = StringUtils.applyRelativePath(getRelativePath(),
                    relativePath);
            
            //组装实际资源对象
            FCResource resource = new LocalAwareFCResource(this.getCatalog(),
                    this.localCatalogResource, this.resourceLoader, pathToUse);
            return resource;
        }
        
        /**
         * @param name
         * @return
         */
        @Override
        public FCResource getChild(String name) {
            //拼接相对路径
            FCResource child = this.resource.getChild(name);
            
            //组装实际资源对象
            FCResource resource = new LocalAwareFCResource(this.getCatalog(),
                    this.localCatalogResource, this.resourceLoader, child);
            return resource;
        }
        
        /**
         * @return
         */
        @Override
        public FCResource getParent() {
            //拼接相对路径
            FCResource parent = this.resource.getParent();
            
            //组装实际资源对象
            FCResource resource = new LocalAwareFCResource(this.getCatalog(),
                    this.localCatalogResource, this.resourceLoader, parent);
            return resource;
        }
        
        /**
         * @param fileFilter
         * @param depthwise
         * @return
         * @throws FileContextResourceException
         */
        @Override
        public List<FCResource> findFiles(FCResourceFilter fileFilter,
                boolean depthwise) throws FileContextResourceException {
            return this.resource.findFiles(fileFilter, depthwise);
        }
        
        /**
         * @return
         */
        @Override
        public boolean exists() {
            return this.resource.exists();
        }
        
        /**
         * @return
         */
        @Override
        public long getSize() {
            return this.resource.getSize();
        }
        
        /**
         * 
         */
        @Override
        public void delete() throws FileContextResourceException {
            //本地文件如果存在则删除
            this.localResource.getFile().deleteOnExit();
            //删除文件
            this.resource.delete();
        }
        
        /**
         * @return
         */
        @Override
        public void deleteQuietly() throws FileContextResourceException {
            //本地文件如果存在则删除
            FileUtils.deleteQuietly(this.localResource.getFile());
            //调用远端删除
            this.resource.deleteQuietly();
        }
        
        /**
         * @throws IOException
         */
        @Override
        public void createFile() throws FileContextResourceException {
            this.resource.createFile();
        }
        
        /**
         * @throws IOException
         */
        @Override
        public void createFolder() throws FileContextResourceException {
            this.resource.createFolder();
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public List<FCResource> getChildren()
                throws FileContextResourceException {
            return this.resource.getChildren();
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public OutputStream getOutputStream()
                throws FileContextResourceException {
            return this.resource.getOutputStream();
        }
        
        /**
         * @param bAppend
         * @return
         * @throws IOException
         */
        @Override
        public OutputStream getOutputStream(boolean bAppend)
                throws FileContextResourceException {
            return this.resource.getOutputStream(bAppend);
        }
        
        /**
         * @param inputStream
         */
        @Override
        public void add(InputStream inputStream)
                throws FileContextResourceException {
            if (this.localResource.exists()) {
                FileUtils.deleteQuietly(this.localResource.getFile());
            }
            this.resource.add(inputStream);
        }
        
        /**
         * @param inputStream
         */
        @Override
        public void save(InputStream inputStream)
                throws FileContextResourceException {
            if (this.localResource.exists()) {
                FileUtils.deleteQuietly(this.localResource.getFile());
            }
            this.resource.save(inputStream);
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public InputStream getInputStream()
                throws FileContextResourceException {
            InputStream ins = null;
            try {
                if (this.localResource.exists()) {
                    ins = this.localResource.getInputStream();
                    return ins;
                } else {
                    synchronized (getLock(getRelativePath())) {
                        if (this.localResource.exists()) {
                            ins = this.localResource.getInputStream();
                            return ins;
                        } else {
                            //获取文件对象并对其进行创建
                            File local = this.localResource.getFile();
                            File folder = local.getParentFile();
                            if (!folder.exists()) {
                                //如果目录不存在，则创建对应目录
                                folder.mkdirs();
                            }
                            if (!local.exists()) {
                                local.createNewFile();
                            }
                            try (InputStream resins = this.resource
                                    .getInputStream();
                                    OutputStream output = localResource
                                            .getOutputStream()) {
                                IOUtils.copy(resins, output);
                            }
                            //因为文件已经被使用了所以需要重新reset
                            ins = this.localResource.getInputStream();
                            return ins;
                        }
                    }
                }
            } catch (IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getInputStream() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
    }
}
